﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Configuration;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using Medianamik.Core.Configuration;
using Medianamik.Core.Security;
using Medianamik.Core.Sugar;

namespace Medianamik.Core
{
    public class NodeInstance
    {
        internal NodeInstance()
        {
        }

        internal NodeInstance(Node pNode, string pCultureName)
        {
            _node = pNode;
            Culture = CultureInfo.GetCultureInfo(pCultureName);
        }

        internal NodeInstance(Guid pNodeInstanceId, Node pNode,
            string pCultureName, bool pIsActive, string pName,
            bool pHasModifications, DateTime pCreatedOn, int approbationLevel)
        {
            InternalID = pNodeInstanceId;
            _node = pNode;
            Culture = CultureInfo.GetCultureInfo(pCultureName);
            _isActive = pIsActive;
            CreatedOn = pCreatedOn;
            _name = pName;
            _hasModifiedProperties = pHasModifications;
            _approbationLevel = approbationLevel;
        }

        public NodeProperty this[string pPropertyName]
        {
            get { return InternalProperties[pPropertyName]; }
        }

        public NodeProperty this[Guid pPropertyId]
        {
            get { return InternalProperties[pPropertyId]; }
        }

        private NodeProperty _nameProperty;
        private NodeProperty NameProperty
        {
            get
            {
                if (_nameProperty == null && Node.HasName)
                {
                    _nameProperty = this[Node.NamePropertyId];
                }
                return _nameProperty;
            }
        }

        private string _extendedData;
        public string ExtendedData
        {
            get
            {
                if(_extendedData == null)
                {
                    var props = Properties.Where(p => !p.NodeTypeProperty.IsGenerated)
                        .Select(p => new NodeExtendedDataProperty(p.Name, p.Value, p.NodeTypeProperty.Relationship))
                        .ToList();

                    var collection = new NodeExtendedDataCollection(props);
                    _extendedData = collection.Serialize();
                }
                return _extendedData;
            }
        }

        private string _name;
        public string Name
        {
            get { return _name; }
            internal set
            {
                if (_name != value)
                {
                    _name = value;
                    IsDirty = true;
                }
            }
        }

        private Guid? _internalID;
        internal Guid InternalID
        {
            get
            {
                return _internalID ??
                       (_internalID = Guid.NewGuid()).Value;
            }
            set { _internalID = value; }
        }

        public bool IsNew { get; internal set; }

        private bool _isDirty;
        public bool IsDirty
        {
            get { return _isDirty; }
            internal set
            {
                _isDirty = value;

                if (value && Node.Status != EntityStates.New)
                    Node.Status = EntityStates.Dirty;
            }
        }

        private bool _toBePublished;
        public bool ToBePublished
        {
            get { return Node.IsVersioned && _toBePublished; }
            set
            {
                if (Node.IsVersioned)
                {
                    _toBePublished = value;
                    IsDirty = true;
                }
            }
        }

        /// <summary>
        ///   Collection de NodeProperties liées au NodeInstance
        /// </summary>
        private NodePropertyCollection _properties;

        internal NodePropertyCollection InternalProperties
        {
            get
            {
                if (_properties == null)
                {
                    _properties = new NodePropertyCollection(this, GetProperties(null));
                    _properties.ForEach(p => p.ValueSet += NodeProperty_ValueSet);
                }

                return _properties;
            }
            set
            {
                if (value.NodeInstance != this)
                {
                    throw new CoreException("this NodeInstance must be passed to NodePropertyCollection");
                }
                _properties = value;
                _properties.ForEach(p => p.ValueSet += NodeProperty_ValueSet);
            }
        }

        public ReadOnlyNodePropertyCollection Properties
        {
            get { return InternalProperties.AsReadOnly(); }
        }

        public bool PropertiesLoaded
        {
            get { return _properties != null; }
        }

        public CultureInfo Culture { get; internal set; }

        private bool _isActive = true;

        public bool IsActive
        {
            get { return _isActive; }
            set
            {
                _isActive = value;
                IsDirty = true;
            }
        }

        public int? Revision
        {
            get
            {
                var props = Properties.Where(p => p.IsOwnProperty);
                if (props.Any(p => !p.Revision.HasValue))
                {
                    return default(int?);
                }
                return props.Max(p => p.Revision);
            }
        }

        public DateTime[] Revisions
        {
            get
            {
                if (!_revisionsAreLoaded)
                {
                    _revisions = NodeManager.GetNodeRevisions(Node.ID, Culture);
                    _revisionsAreLoaded = true;
                }

                return _revisions;
            }
        }

        private DateTime[] _revisions;
        private bool _revisionsAreLoaded;

        private List<Publication> _publications;
        public IEnumerable<Publication> Publications
        {
            get { return _publications ?? (_publications = 
                NodeManager.GetNodePublications(Node.ID, Culture).ToList()); }
        }

        private List<Publication> _newPublications;
        public List<Publication> NewPublications
        {
            get
            {
                return _newPublications ?? (_newPublications =
                    NodeManager.GetNewNodePublications(Node.ID, Culture).ToList());
            }
        }

        public DateTime CreatedOn { get; private set; }

        private bool _hasModifiedProperties = true;

        public bool HasModifiedProperties
        {
            get { return _hasModifiedProperties; }
            set
            {
                if (Node.NodeType.IsVersioned)
                    _hasModifiedProperties = value;

                IsDirty = true;
            }
        }

        private Node _node;

        public Node Node
        {
            get
            {
                //Initilisé au constructeur
                return _node;
            }
            internal set
            {
                if (value == null)
                {
                    throw new ArgumentNullException("Node");
                }
                _node = value;
            }
        }

        public bool HasUrl
        {
            get { return (Properties.Any(p => p.Name.Equals("Url", StringComparison.InvariantCultureIgnoreCase))); }
        }

        public List<NodeProperty> GetPropertiesForRevision(DateTime? pRevision)
        {
            return pRevision.HasValue ? GetProperties(pRevision) : Properties.ToList();
        }

        public override string ToString()
        {
            return string.Concat("[NodeInstance:", Node.NodeType.Name, "|", Culture.Name, "|", Revision, "]");
        }

        internal void MarkSaved()
        {
            IsNew = false;
            IsDirty = false;
            foreach (var prop in Properties)
            {
                prop.MarkSaved();
            }
        }

        private List<NodeProperty> GetProperties(DateTime? pRevision)
        {
            var props = Node.GetNodeProperties(pRevision)
                .Where(
                    p =>
                    p.Culture.Name.EqualsIgnoreCase(Culture.Name) ||
                    p.Culture.Name.EqualsIgnoreCase(CultureInfo.InvariantCulture.Name));

            return props.ToList();
        }

        private void NodeProperty_ValueSet(object sender, EventArgs e)
        {
            var prop = (NodeProperty)sender;
            if (prop.IsDirty) //|| Node.NodeType.IsVersioned)
            {
                HasModifiedProperties = true;
            }
            if (prop.NodeTypePropertyID == Node.NamePropertyId)
            {
                Name = Convert.ToString(prop.Value);
            }
        }

        protected internal void SetRevision(int revision)
        {
            Properties.ForEach(p => p.Revision = revision);
            HasModifiedProperties = false;
        }

        // Approbation
        private int _approbationLevel;
        public int ApprobationLevel
        {
            get
            {
                return _approbationLevel;
            }
            internal set
            {
                _approbationLevel = value;
                IsDirty = true;
            }
        }

        public void Approve()
        {
            SetApprobationLevel(true);
        }

        public void SetApprobationLevel(bool toNextLevel)
        {
            var level = Roles.GetApprobationLevelForUser(Node);

            ApprobationLevel = level + (toNextLevel ? 1 : 0);
        }

        public void ResetApprobationLevel()
        {
            ApprobationLevel = 0;
        }

        public bool IsPendingApprobation
        {
            get { return ApprobationLevel > 0; }
        }
    }

    public class NodeInstanceCollection : KeyedCollection<CultureInfo, NodeInstance>
    {
        /// <summary>
        ///   Le NodeInstance propriétaire de la collection
        /// </summary>
        internal Node Node { get; private set; }

        /// <summary>
        ///   Créer une instance de la classe, liée à un NodeInstance
        /// </summary>
        /// <param name = "pOwner">Le NodeInstance propriétaire de la collection</param>
        internal NodeInstanceCollection(Node pOwner)
            : base(null, 0)
        {
            if (pOwner == null)
            {
                throw new ArgumentNullException("pOwner");
            }

            Node = pOwner;

            //this.AddRange(Node.FetchingStategy.GetInstances(Node));
        }

        protected override void SetItem(int index, NodeInstance item)
        {
            throw new NotSupportedException("Cannot re-assign a NodeInstance");
        }

        protected override void RemoveItem(int index)
        {
            if (Items.Count <= 1)
            {
                throw new Exception("Nodes must contain at least one instance.");
            }
            var item = Items[index];
            base.RemoveItem(index);
            _removed.Add(item);
        }

        protected override void ClearItems()
        {
            _removed.AddRange(Items);
            base.ClearItems();
        }

        protected override void InsertItem(int index, NodeInstance item)
        {
            base.InsertItem(index, item);
        }

        /// <summary>
        ///   Collection NodeInstance à supprimer dans la BD
        /// </summary>
        private readonly List<NodeInstance> _removed = new List<NodeInstance>();

        internal ReadOnlyCollection<NodeInstance> Removed
        {
            get { return _removed.AsReadOnly(); }
        }

        public NodeInstance this[string pCultureName]
        {
            get
            {
                return this[CultureInfo.GetCultureInfo(pCultureName)];
                //if (pCultureName == null) { throw new ArgumentNullException("pCultureName"); }
                //NodeInstance inst = this.FirstOrDefault(p => p.Culture.Name.Equals(pCultureName, StringComparison.OrdinalIgnoreCase));

                //if (inst == null)
                //{
                //    throw new CoreException(string.Concat("No instance with CultureName '", pCultureName, "' was found"));
                //}

                //return inst;
            }
        }

        //public NodeInstance this[CultureInfo pCulture]
        //{
        //    get
        //    {
        //        return this[pCulture.Name];
        //    }
        //}

        //private IEnumerable<NodeInstance> GetNodeInstances()
        //{
        //    return DALProviderManager.DALProvider.NodeDALProvider.GetNodeInstances(Node);
        //}

        internal void AddRange(IEnumerable<NodeInstance> items)
        {
            if (Node.IsLanguageNeutral && !items.Any(i => i.Culture.Name.Equals("")))
            {
                throw new ApplicationException(String.
                                                   Format(
                                                       "Cannot add non language neutral instance to neutral language node. ID: {0} Type: {1}",
                                                       Node.ID, Node.NodeType.Name));
            }

            if (!Node.IsLanguageNeutral && items.Any(i => i.Culture.Name.Equals("")))
            {
                throw new ApplicationException(String.
                                                   Format(
                                                       "Cannot add language neutral instance to non neutral language node. ID: {0} Type: {1}",
                                                       Node.ID, Node.NodeType.Name));
            }

            foreach (var item in items)
            {
                if (!item.Node.ID.Equals(Node.ID))
                {
                    throw new CoreException("NodeID must be equal to NodeInstance.ID");
                }

                item.Node = Node;
                Add(item);
            }
        }

        public bool Exists(string pCultureName)
        {
            return Exists(CultureInfo.GetCultureInfo(pCultureName));
            //return Items.Any(p => p.Culture.Name.EqualsIgnoreCase(pCultureName));
        }

        public bool Exists(CultureInfo pCulture)
        {
            if (pCulture == null)
            {
                throw new ArgumentNullException("pCulture");
            }
            return Dictionary.ContainsKey(pCulture);
            //return Exists(pCulture.Name); 
        }

        public void ForEach(Action<NodeInstance> pAction)
        {
            foreach (var item in Items)
            {
                pAction(item);
            }
        }

        public ReadOnlyNodeInstanceCollection AsReadOnly()
        {
            return new ReadOnlyNodeInstanceCollection(Items);
        }

        protected override CultureInfo GetKeyForItem(NodeInstance item)
        {
            return item.Culture;
        }
    }

    public class ReadOnlyNodeInstanceCollection : ReadOnlyCollection<NodeInstance>
    {
        internal ReadOnlyNodeInstanceCollection(IList<NodeInstance> list)
            : base(list)
        {
        }

        public NodeInstance this[string pCultureName]
        {
            get
            {
                if (pCultureName == null)
                {
                    throw new ArgumentNullException("pCultureName");
                }
                var inst =
                    this.FirstOrDefault(p => p.Culture.Name.Equals(pCultureName, StringComparison.OrdinalIgnoreCase));

                if (inst == null)
                {
                    throw new CoreException(string.Concat("No instance with CultureName '", pCultureName, "' was found"));
                }

                return inst;
            }
        }

        public NodeInstance this[CultureInfo pCulture]
        {
            get { return this[pCulture.Name]; }
        }

        public bool Exists(string pCultureName)
        {
            return Items.Any(p => p.Culture.Name.EqualsIgnoreCase(pCultureName));
        }

        public bool Exists(CultureInfo pCulture)
        {
            if (pCulture == null)
            {
                throw new ArgumentNullException("pCulture");
            }
            return Exists(pCulture.Name);
        }

        public void ForEach(Action<NodeInstance> pAction)
        {
            foreach (var item in Items)
            {
                pAction(item);
            }
        }
    }
}